﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

using DigibizTree;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.datatype.controls;
using umbraco.DataLayer;
using umbraco.editorControls.imagecropper;
using umbraco.interfaces;
using umbraco.IO;
using umbraco.cms.businesslogic.property;
using System.IO;
using umbraco.cms.businesslogic.media;
using System.Net;

namespace DigibizAdvancedMediaPicker
{
    public class DAMP_Helper
    {
        protected static ISqlHelper SqlHelper
        {
            get { return umbraco.BusinessLogic.Application.SqlHelper; }
        }

        #region Exceptions

        /// <summary>
        /// Log an exception.
        /// </summary>
        /// <param name="ex"></param>
        public static void LogException(Exception ex)
        {
            LogException(ex, null);
        }

        /// <summary>
        /// Log an exception.
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="nodeId"></param>
        /// <param name="type"></param>
        public static void LogException(Exception ex, int? nodeId)
        {
            DigibizMediaHelper.LogException(ex, nodeId);
        }

        #endregion

        #region Update DAMP

        /// <summary>
        /// Get a list of DAMP_UpdateDocument-objects which contain data of what needs to be updated.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <param name="id"></param>
        /// <param name="alias"></param>
        /// <param name="mediaId"></param>
        /// <returns></returns>
        public static List<DAMP_UpdateDocument> GetUpdateDocuments(string dampXml, int id, string alias, int mediaId)
        {
            //In here we'll store the data which needs to be updated.
            List<DAMP_UpdateDocument> updateDocuments = new List<DAMP_UpdateDocument>();

            //Get the xml.
            XDocument xmlDocument = XDocument.Parse(dampXml);

            //Get the foundIds from the the xml.
            List<int> foundIds =
                (
                    from mediaItemElement in xmlDocument.Descendants("mediaItem")
                    select DigibizMediaHelper.GetMediaId((XElement)mediaItemElement.FirstNode)
                ).ToList();

            foreach (int foundId in foundIds)
            {
                if (foundId == mediaId)
                {
                    //If the foundId is the id of the media which is updated store it in the list which we'll use later.
                    updateDocuments.Add(new DAMP_UpdateDocument(xmlDocument, id, alias));
                }
            }

            return updateDocuments;
        }

        /// <summary>
        /// This method updates the DAMP xml with the updated media item.
        /// </summary>
        /// <param name="updateData"></param>
        /// <param name="mediaId"></param>
        /// <param name="deleteMediaItem"></param>
        public static void UpdateDAMPXml(XDocument dampXml, int mediaId, bool deleteMediaItem)
        {
            //Get the mediaItem elements which will be updated.
            List<XElement> mediaItemElements =
                (
                    from mediaItemElement in dampXml.Descendants("mediaItem")
                    where DigibizMediaHelper.GetMediaId((XElement)mediaItemElement.FirstNode) == mediaId
                    select mediaItemElement
                ).ToList();

            foreach (XElement mediaItemElement in mediaItemElements)
            {
                if (deleteMediaItem)
                {
                    //Remove the media element because the media item is deleted.
                    mediaItemElement.Remove();
                }
                else
                {
                    //Remove the old media item.
                    mediaItemElement.Elements().Remove();

                    //Get the xml from the cmsContentXml table. We don't use library.GetMedia here because of some cache problems and because it doesn't support CDATA.
                    XDocument xmlDocument = XDocument.Load(SqlHelper.ExecuteXmlReader("select xml from cmsContentXml where nodeID = " + mediaId.ToString()));

                    //Save the media item in the xml.
                    mediaItemElement.Add(xmlDocument.Root);
                }
            }
        }

        #endregion

        #region Util

        /// <summary>
        /// Get the URL of a single media item of any media type from a DAMP XML string.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static string GetUrl(string dampXml)
        {
            return GetPath(dampXml, "//umbracoFile/text()");
        }

        /// <summary>
        /// Get the URL of a single media item of the Image media type from a DAMP XML string.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static string GetImageUrl(string dampXml)
        {
            return GetPath(dampXml, "//Image/umbracoFile/text()");
        }

        /// <summary>
        /// Get the URL of a single media item of the File media type from a DAMP XML string.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static string GetFileUrl(string dampXml)
        {
            return GetPath(dampXml, "//File/umbracoFile/text()");
        }

        /// <summary>
        /// Return all the media items of the Image media type.
        /// </summary>
        /// <param name="dampDetails"></param>
        /// <returns></returns>
        public static IEnumerable<DAMP_File> GetImages(string dampXml)
        {
            return GetMediaTypeFiles(dampXml, "Image");
        }

        /// <summary>
        /// Return all the media items of the file media type.
        /// </summary>
        /// <param name="dampDetails"></param>
        /// <returns></returns>
        public static IEnumerable<DAMP_File> GetFiles(string dampXml)
        {
            return GetMediaTypeFiles(dampXml, "File");
        }

        /// <summary>
        /// Return the icon/thumbnail of the first media item.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static string GetIcon(string dampXml)
        {
            return GetIcons(dampXml).FirstOrDefault();
        }

        /// <summary>
        /// Return the icon/thumbnail and name of the first media item.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static DAMP_IconFile GetIconAndName(string dampXml)
        {
            return GetIconsAndNames(dampXml).FirstOrDefault();
        }

        /// <summary>
        /// Return the icon/thumbnail and name (custom element) of the first media item.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <param name="elementName">The name of the element you want to return.</param>
        /// <returns></returns>
        public static DAMP_IconFile GetIconAndName(string dampXml, string elementName)
        {
            return GetIconsAndNames(dampXml, elementName).FirstOrDefault();
        }

        /// <summary>
        /// Return all the icons/thumbnails of all the media items.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static IEnumerable<string> GetIcons(string dampXml)
        {
            XDocument xmlDocument = XDocument.Parse(dampXml);

            return (
                        from mediaElement in xmlDocument.Descendants("mediaItem")
                        select DigibizMediaHelper.GetMediaIcon(mediaElement)
                    );
        }

        /// <summary>
        /// Return all the icons/thumbnails and names of all the media items.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <returns></returns>
        public static IEnumerable<DAMP_IconFile> GetIconsAndNames(string dampXml)
        {
            XDocument xmlDocument = XDocument.Parse(dampXml);

            return (
                        from mediaItemElement in xmlDocument.Descendants("mediaItem")
                        select new DAMP_IconFile
                        {
                            Name = ((XElement)mediaItemElement.FirstNode).Attribute("nodeName").Value,
                            Url = DigibizMediaHelper.GetMediaLink((XElement)mediaItemElement.FirstNode),
                            IconUrl = DigibizMediaHelper.GetMediaIcon((XElement)mediaItemElement.FirstNode)
                        }
                    );
        }

        /// <summary>
        /// Return the icons/thumbnails and names (custom element) of all the media items.
        /// </summary>
        /// <param name="dampXml"></param>
        /// <param name="elementName">The name of the element you want to return.</param>
        /// <returns></returns>
        public static IEnumerable<DAMP_IconFile> GetIconsAndNames(string dampXml, string elementName)
        {
            XDocument xmlDocument = XDocument.Parse(dampXml);

            return (
                        from mediaItemElement in xmlDocument.Descendants("mediaItem")
                        select new DAMP_IconFile
                        {
                            Name = ((XElement)mediaItemElement.FirstNode).Attribute("nodeName").Value,
                            Url = DigibizMediaHelper.GetMediaLink((XElement)mediaItemElement.FirstNode),
                            IconUrl = DigibizMediaHelper.GetMediaIcon((XElement)mediaItemElement.FirstNode)
                        }
                    );
        }

        /// <summary>
        /// If you create a media item the image crops aren't created by default. You first need to press the save button before crops are created.
        /// If you call this method in the before save event it will create the crops without that you need to press the save button.
        /// It's recommended to call this method in a try-catch and check if the selected property alias (usually umbracoFile) for cropping has a path.
        /// Make sure the culture is the same everywhere. You could run into this problem: http://umbraco.codeplex.com/workitem/30273.
        /// This only works with the default image cropper that's part of Umbraco 4.5+.
        /// </summary>
        /// <param name="mediaId">The media item that needs to be cropped.</param>
        /// <param name="onlyIfEmpty">If true the image will only be cropped if no crops are available. If false the image will always be cropped.</param>
        public static void CallImagecropper(Media media, bool onlyIfEmpty)
        {
            IDataType imageCropper = new Factory().GetNewObject(new Guid("7A2D436C-34C2-410F-898F-4A23B3D79F54"));

            //Get the propertyid and datatypeid of this media item.
            string sql = string.Format(@"
                    select cpd.id as id, cpt.dataTypeId as datatypeid, cpt.Alias as alias from cmsPropertyData cpd
                        inner join cmsPropertyType cpt on cpd.propertytypeid = cpt.Id
                        inner join cmsDataType cdt on cpt.dataTypeId = cdt.nodeId
                    where cpd.contentNodeId = {0}
                    and cdt.controlId = '{1}'", media.Id, imageCropper.Id);

            using (IRecordsReader dr = SqlHelper.ExecuteReader(sql))
            {
                while (dr.Read())
                {
                    int propertyId = dr.GetInt("id");
                    string alias = dr.GetString("alias");

                    //Set the values on the imagecropper datatype that was found on the media type.
                    imageCropper.Data.PropertyId = propertyId;
                    imageCropper.DataTypeDefinitionId = dr.GetInt("datatypeid");

                    Property propImagecropper = null;
                    bool isUpdated = false;
                    string configuration = ((PrevalueEditor)imageCropper.PrevalueEditor).Configuration;

                    if (onlyIfEmpty)
                    {
                        //Get the property where the crop data is stored.
                        propImagecropper = new Property(propertyId);

                        string[] configFiles = configuration.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                        if (configFiles.Any())
                        {
                            //Get the property that is used for the Image Cropper (the first value from the configuration string).
                            Property pathProp = media.getProperty(configFiles[0]);
                            if (pathProp != null && pathProp.Value != null && !string.IsNullOrEmpty(pathProp.Value.ToString()))
                            {
                                //Get the date when the file was last modified.
                                DateTime modification = File.GetLastWriteTime(HttpContext.Current.Server.MapPath(pathProp.Value.ToString()));

                                //Check if the file is updated less than a minute ago. The cropper must be called if it is.
                                isUpdated = modification > DateTime.Now.AddMinutes(-1);
                            }
                        }
                    }

                    if (!onlyIfEmpty || isUpdated || propImagecropper != null && (propImagecropper.Value == null || string.IsNullOrEmpty(propImagecropper.Value.ToString())))
                    {
                        //Now that all properties are set correctly call the imagecropper and crop the image.
                        DAMP_Imagecropper dataEditor = new DAMP_Imagecropper(imageCropper.Data, configuration);
                        dataEditor.Page = new Page();
                        dataEditor.CallInit();
                        dataEditor.Save();

                        if (umbraco.GlobalSettings.VersionMajor > 4)
                        {
                            //Set the value on the media object so it will be part of the transaction in v6 or higher.
                            media.getProperty(alias).Value = imageCropper.Data.Value;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Download a file from the remote location and save it to disk.
        /// </summary>
        /// <param name="remoteFileName">Path to remote file.</param>
        /// <param name="localFileName">Path to the local file to save.</param>
        public static void SaveWebFile(string remoteFile, string localFile)
        {
            WebClient webClient = new WebClient();

            try
            {
                FileInfo file = new FileInfo(localFile);

                if (!file.Directory.Exists)
                {
                    //If the directory doesn't exist then create it.
                    file.Directory.Create();
                }

                webClient.DownloadFile(remoteFile, localFile);
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
            finally
            {
                webClient.Dispose();
            }
        }

        #region Razor

        /// <summary>
        /// Returns the url for a given crop name using the built in Image Cropper datatype.
        /// It accepts a razor dynamic xml property.
        /// </summary>
        /// <param name="mediaItem">The mediaItem XML property used in razor. Example: @node.slider.mediaItem. It also accepts the child element of mediaItem like "Image".</param>
        /// <param name="cropName">Name of crop to get url for.</param>
        /// <returns>Emtpy string or url.</returns>
        public static string GetImageCropperUrl(umbraco.MacroEngines.DynamicXml mediaItem, string cropName)
        {
            string cropUrl = string.Empty;

            /*
             * Example xml : 
             * 
			 * <mediaItem>
             *  <Image id="1307" version="9eac3ee8-e18f-410d-988a-f257d53ab7e6" parentID="1274" level="4" writerID="0" nodeType="1276" template="0" sortOrder="4" createDate="2011-04-28T14:50:31" updateDate="2011-04-28T14:50:38" nodeName="slide4Image" urlName="slide4image" writerName="admin" nodeTypeAlias="ImageHomepageSlider" path="-1,1123,1124,1274,1307">
             *     <umbracoFile>/media/5031/slide4image.jpg</umbracoFile>
             *     <umbracoWidth>960</umbracoWidth>
             *     <umbracoHeight>446</umbracoHeight>
             *     <umbracoBytes>128397</umbracoBytes>
             *     <umbracoExtension>jpg</umbracoExtension>
             *     <resized><![CDATA[/media/5031/slide4image_resized.jpg]]></resized>
             *     <slider>
             *       <crops date="28/04/2011 14:50:32">
             *         <crop name="slider" x="0" y="0" x2="960" y2="446" url="/media/5031/slide4image_resized_slider.jpg" />
             *         <crop name="sliderSmall" x="0" y="0" x2="200" y2="100" url="/media/5032/slide4image_resized_sliderSmall.jpg" />
             *       </crops>
             *     </slider>
             *   </Image>
             * </mediaItem>
			 * 
			 */

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(mediaItem.BaseElement.ToString());
            XmlNode cropNode = xmlDocument.SelectSingleNode("descendant::crops/crop[@name='" + cropName + "']");
            
            if (cropNode != null)
            {
                cropUrl = cropNode.Attributes.GetNamedItem("url").InnerText;
            }

            return cropUrl;
        }

        #endregion

        #endregion

        #region Internal

        /// <summary>
        /// Helper method to ensure the pesistence cookie is cleared.
        /// This is used on app startup and after editing the pre-value editor.
        /// </summary>
        internal static void ClearCookiePersistence()
        {
            if (HttpContext.Current == null)
            {
                return;
            }

            if (HttpContext.Current.Response.Cookies[DigibizConstants.COOKIENAME] != null)
            {
                HttpContext.Current.Response.Cookies[DigibizConstants.COOKIENAME].Expires = DateTime.Now.AddDays(-1);
            }
        }

        #endregion

        #region Other - Methods

        private static string GetPath(string dampXml, string xpath)
        {
            if (string.IsNullOrEmpty(dampXml))
            {
                return string.Empty;
            }
            else
            {
                var url = XDocument.Parse(dampXml).CreateNavigator().SelectSingleNode(xpath);
                return url == null ? "" : url.Value;
            }
        }

        private static IEnumerable<DAMP_File> GetMediaTypeFiles(string dampXml, string mediaType)
        {
            if (string.IsNullOrEmpty(dampXml))
            {
                return new List<DAMP_File>(0);
            }

            var doc = XDocument.Parse(dampXml);

            return (
                from f in doc.Descendants(mediaType)
                select new DAMP_File
                {
                    Name = f.Attribute("nodeName").Value,
                    Url = f.Descendants("umbracoFile").First().Value
                });
        }

        #endregion

    }
}